package server.world;

import general.datastructures.PriorityQueue;
import general.datastructures.Vector2f;
import general.datastructures.Waypoint;
import general.exceptions.DestinationNotPassableException;
import general.exceptions.Exception;
import general.exceptions.NoRouteFoundException;
import general.exceptions.OutOfMapException;
import general.interfaces.IUnit;

import java.util.Vector;

import server.world.RouteStorage.RouteRequest;





/**
 * Class that extends {@link Thread}, for calculating Routes in the map
 * 
 * @version 0.5.0
 * @since 0.4.0
 * @author tim
 *
 */
public class RouteCalculator extends Thread{

	private OldMap map;
	
	private RouteStorage storage;
	
	private boolean isRunning = true;
	
	public RouteCalculator(OldMap m)
	{
		storage = new RouteStorage();
		this.map = m;
	}
	
	public RouteStorage getRouteStorage()
	{
		return this.storage;
	}
	
	@Override
	public void run() {
	
		while (isRunning)
		{
			RouteRequest req=null;
		
			do
			{
				try {
					//System.out.println("RouteCalculator: Idle");
					Thread.sleep(10);
					if(!isRunning)
					{
						break;
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				req = storage.getNextRequest();
			}while(req == null);

			try{
				System.out.println("RouteCalculator: Calculating Route from " + req.from +" to "+req.to);
				Vector<Vector2f> wc_route = getRoute(req.from, req.to, req.vehicletype);
				System.out.println(wc_route.toString());
				storage.addRoute(req.owner, wc_route);
			}catch (java.lang.Exception | Exception e) {
				e.printStackTrace();
			}
						
		}
		
	}
	
	public void kill()
	{
		this.isRunning = false;
	}
	
	/**
	 * 
	 * Calculates the Route between Origin and Destination, using the A*-Algorithm
	 * 
	 * @param from Origin {@link Vector2f}
	 * @param to Destination {@link Vector2f}
	 * @param vehicletype Vehicletype, for named static final int parameter see {@link IUnit}
	 * @return List of {@link Waypoint}s which lead from the Origin to the Destination, 
	 * @throws Exception Different {@link Exception}s, depending on the error ({@link OutOfMapException}, {@link DestinationNotPassableException}, {@link NoRouteFoundException})
	 */
	private Vector<Vector2f> getRoute(Vector2f mc_from, Vector2f mc_to, int vehicletype) throws Exception
	{

		// grundsetzliche Ueberpruefungen
		if(!map.isInMap(mc_from))
		{
			throw new OutOfMapException("Ausgangspunkt liegt ausserhalb der Karte!",mc_from);
		}
		if(!map.isInMap(mc_to))
		{
			throw new OutOfMapException("Zielpunkt liegt ausserhalb der Karte!",mc_to);
		}
		if(!map.isPassable(vehicletype, mc_from))
		{
			throw new DestinationNotPassableException("Ausgangspunkt ist nicht befahrbar!", mc_from);
		}
		if(!map.isPassable(vehicletype, mc_to))
		{
			throw new DestinationNotPassableException("Ziel ist nicht befahrbar!", mc_to);
		}
		if(mc_from.equals(mc_to))
		{
			throw new Exception("Ausgangspunkt und Ziel sind identisch!");
		}

		System.out.println("Exceptions ueberlebt");

		boolean routeFound = false;

		PriorityQueue openlist = new PriorityQueue();
		Vector<Waypoint> closedlist = new Vector<Waypoint>();

		Waypoint wp_to = new Waypoint(mc_to,0);
		Waypoint currentWP;

		Vector<Waypoint> newWaypoints;

		//System.err.println("Fuege Punkt to zu openlist hinzu: " + to.toString());
		openlist.insert(wp_to, 0);

		do{
			//System.err.println(openlist.toString());

			currentWP = openlist.getFirst();
			//System.err.println("Hole Node von der openlist: " + currentWP.toString());


			//if (currentWP.getVector2f().equals(from))
			if (currentWP.getVector2f().isInRange(mc_from, 0.707f))
			{
				System.out.println("Weg gefunden!!");
				routeFound = true;
				break;
			}

			closedlist.add(currentWP);

			// Expand Node
			Vector2f currentpoint = currentWP.getVector2f();

			newWaypoints = new Vector<Waypoint>(8);

			//8er Nachbarschaft hinzufuegen
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x()-1 , currentpoint.y()-1 ), 	Math.sqrt(2)	,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x()-1 , currentpoint.y() ), 		1			,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x()-1 , currentpoint.y()+1 ), 	Math.sqrt(2)	,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x() , currentpoint.y()-1 ), 		1			,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x() , currentpoint.y()+1 ), 		1			,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x()+1 , currentpoint.y()-1 ), 	Math.sqrt(2)	,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x()+1 , currentpoint.y() ), 		1			,currentWP));
			newWaypoints.add(new Waypoint(new Vector2f(currentpoint.x()+1 , currentpoint.y()+1 ), 	Math.sqrt(2)	,currentWP));

			//System.err.println("Alle punkte zum vektor hinzugefuegt");

			for(Waypoint wp : newWaypoints)
			{
				if ( (closedlist.contains(wp)) || (!map.isInMap(wp.getVector2f())) || (!map.isPassable(vehicletype, wp.getVector2f())) )
				{
					continue;
				}

				double estimatedDistance = wp.getDistance()+wp.getVector2f().getDistanceTo(mc_from);

				openlist.insert(wp, estimatedDistance);
			}

		}
		while (!openlist.isEmpty());

		if (routeFound)
		{		
			Vector<Vector2f> mc_wps = new Vector<Vector2f>();
			Vector<Vector2f> wc_waypoints = new Vector<Vector2f>();
			currentWP.getRouteList(mc_wps);
			for(Vector2f wp:mc_wps)
			{
				wc_waypoints.add(general.helperclasses.Math.mapToWorld(wp, map.getTilesize()));
			}
			wc_waypoints.remove(0);
			return wc_waypoints;
		}
		else
		{
			throw new NoRouteFoundException("Konnte keine Route finden!", mc_from, mc_to);		
		}
	}
	
}
